package com.diy.sigmund.util;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <pre code="class">
 * 根据开源工具二次开发，适配了入参集合类型
 * 该工具只能处理mybatis，无法处理mybatisPlus的基类BaseMapper
 *
 * 总体思路就三步：
 *
 * 1.通过反射获得要执行定义的Mapper本类的所有方法，不包含父类BaseMapper的方法
 * 2.获得方法中的参数，并赋值
 * 3.执行
 *
 * 自测demo:
 *     &#064;Autowired
 *     SqlSessionFactory sqlSessionFactory;
 *
 *         // AutoTestMapperUtil autoTestMapperUtil = new AutoTestMapperUtil("存放Mapper全路径名");
 *         AutoTestMapperUtil autoTestMapperUtil = new AutoTestMapperUtil("F:\\project\\knight\\gitee\\spring-cloud\\nacos-parent\\nacos-spring-cloud-discovery-config-example\\async-transactional\\src\\main\\java\\com\\diy\\sigmund\\mapper");
 *         autoTestMapperUtil.openSqlSession(sqlSessionFactory);
 *     </pre>
 *
 * @see <a href="https://zhuanlan.zhihu.com/p/88870923">如何批量测试Mybatis项目中SQL是否正确</a>
 **/
public class AutoTestMapperUtil {

    private static final Logger log = LoggerFactory.getLogger(AutoTestMapperUtil.class);
    private static String PACK_PATH = "";
    private static final Set<String> FILE_NAME = new LinkedHashSet<>();
    private static final String PATH_PATTERN = "import [a-z,A-Z,/.]+;";
    private static final String PACK_PATTERN = "package [a-z,A-Z,/.]+;";
    private static final String IMPORT_REGEX = "import ";
    private static final String PACK_REGEX = "package ";
    private static final String SUCCESS_FLG = "success";
    private static final String FAIL_FLG = "fail";

    private Configuration configuration;
    private static final Set<Class> TYPE_SET = Stream.of(
            String.class,
            byte.class, Byte.class,
            short.class, Short.class,
            int.class, Integer.class,
            long.class, Long.class,
            float.class, Float.class,
            double.class, Double.class,
            boolean.class, Boolean.class,
            char.class, Character.class
    ).collect(Collectors.toSet());

    /** 基本类型的class */
    private static final Map<String, Class<?>> PRIMITIVE_CLAZZ;

    static {
        PRIMITIVE_CLAZZ = new LinkedHashMap<>();
        PRIMITIVE_CLAZZ.put("byte", Byte.class);
        PRIMITIVE_CLAZZ.put("short", Short.class);
        PRIMITIVE_CLAZZ.put("int", Integer.class);
        PRIMITIVE_CLAZZ.put("long", Long.class);
        PRIMITIVE_CLAZZ.put("float", Float.class);
        PRIMITIVE_CLAZZ.put("double", Double.class);
        PRIMITIVE_CLAZZ.put("boolean", Boolean.class);
        PRIMITIVE_CLAZZ.put("char", Character.class);
    }

    public AutoTestMapperUtil() {
    }

    public AutoTestMapperUtil(String path) throws IOException, ClassNotFoundException {
        String mapperContent = getFileContent(path);
        String[] pathArr = matchMethod(PATH_PATTERN, mapperContent).split(";");
        for (int i = 0; i < pathArr.length; i++) {
            pathArr[i] = pathArr[i].replaceAll(IMPORT_REGEX, "");
            Class cls = Class.forName(pathArr[i]);
            if (!cls.isInterface()) {
                //把实体类放入类型集合中
                TYPE_SET.add(cls);
            }
        }
        //获得全路径名的前缀
        String[] packPathArr = matchMethod(PACK_PATTERN, mapperContent).split(";");
        String packPath = packPathArr[0].replaceAll(PACK_REGEX, "").replaceAll(";", "");
        PACK_PATH = packPath;
    }

    public void openSqlSession(SqlSessionFactory sqlSessionFactory)
            throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException,
            InvocationTargetException, IntrospectionException {
        this.configuration = sqlSessionFactory.getConfiguration();
        List<String> invokeSuccess = new ArrayList<>();
        List<String> invokeFail = new ArrayList<>();
        // 自测更改1
        List<String> collect = FILE_NAME.stream()
                .filter(s -> equalsAny(s, ""))
                .collect(Collectors.toList());
        // for (String fileName : collect) {
        for (String fileName : FILE_NAME) {
            Class cls = Class.forName(PACK_PATH + "." + fileName);
            //添加Mapper
            if (!sqlSessionFactory.getConfiguration().hasMapper(cls)) {
                sqlSessionFactory.getConfiguration().addMapper(cls);
            }
            //获得Mapper
            Object mapper = sqlSessionFactory.openSession().getMapper(cls);
            //反射执行Mapper的方法
            Map<String, List<String>> resultMap = autoTestInvoke(cls, mapper);
            invokeSuccess.addAll(resultMap.get(SUCCESS_FLG));
            invokeFail.addAll(resultMap.get(FAIL_FLG));
        }
        invokeSuccess.forEach(log::info);
        log.info("-------------------");
        invokeFail.forEach(log::info);
        log.info("invokeSuccess.size:{}", invokeSuccess.size());
        log.info("invokeFail.size:{}", invokeFail.size());
    }

    private Map<String, List<String>> autoTestInvoke(Class c, Object o)
            throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException,
            IntrospectionException {
        // 获得本类声明的方法
        Method[] declaredMethods = c.getDeclaredMethods();
        // 获得本类及父类的方法
        // Method[] declaredMethods = c.getMethods();
        String fileName = c.getName().substring(c.getName().lastIndexOf(".")).substring(1);
        List<String> invokeSuccess = new ArrayList<>();
        List<String> invokeFail = new ArrayList<>();
        Map<String, List<String>> resultMap = new HashMap<>();
        //给参数赋初始值
        for (Method method : declaredMethods) {
            // 自测更改2
            if (!equalsAny(method.getName(), "insertOneMap")) {
                // continue;
            }
            List<Object> list = new ArrayList<>();
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int index = 0; index < parameterTypes.length; index++) {
                Object par = assignmentPrimitive(parameterTypes[index], method, c, index);
                list.add(par);
            }
            try {
                method.invoke(o, list.toArray());
                invokeSuccess.add("Success: " + fileName + "." + method.getName());
            } catch (Exception e) {
                log.error("sql执行失败", e);
                if (e.getCause() != null) {
                    String errorInf = e.getCause().getMessage();
                    if (errorInf.contains("MySQLSyntaxErrorException")) {
                        invokeFail.add("|Error: |" + fileName + "." + method.getName() + "| " + errorInf.substring(
                                errorInf.lastIndexOf("MySQLSyntaxErrorException:") + "MySQLSyntaxErrorException:"
                                        .length()).replaceAll("'", "`") + "|");
                    } else {
                        if (errorInf.contains("Cause:")) {
                            invokeFail.add("|Error: |" + fileName + "." + method.getName() + "| " + errorInf
                                    .substring(errorInf.lastIndexOf("Cause:") + "Cause:".length()).replaceAll("'", "`")
                                    + "|");
                        } else {
                            invokeFail.add("|Error: |" + fileName + "." + method.getName() + "| " + errorInf + "|");
                        }
                    }
                } else {
                    invokeFail.add("|Error: |" + fileName + "." + method.getName() + "| " + e);
                }
            }

        }
        resultMap.put(SUCCESS_FLG, invokeSuccess);
        resultMap.put(FAIL_FLG, invokeFail);
        return resultMap;
    }

    /** 为基础类型和包装类赋值 */
    private Object assignmentPrimitive(Class cls, Method method, Class c, int index)
            throws IllegalAccessException, InvocationTargetException, InstantiationException, IntrospectionException {
        Object par = new Object();
        if (TYPE_SET.contains(cls)) {
            if (cls.equals(String.class)) {
                par = "1";
            } else if (equalsAny(cls.getName(), "java.util.List", "java.util.ArrayList", "java.util.Collection")) {
                par = assignmentCollection(method, index, par, o -> {
                    List<Object> objects = new ArrayList<>();
                    objects.add(o);
                    return objects;
                });
            } else if (equalsAny(cls.getName(), "java.util.Set", "java.util.HashSet")) {
                par = assignmentCollection(method, index, par, o -> {
                    Set<Object> objects = new HashSet<>();
                    objects.add(o);
                    return objects;
                });
            } else {
                try {
                    par = cls.newInstance();
                    assignment(cls, par);
                } catch (InstantiationException e) {
                    par = handleObject(cls, par, e);
                }
            }
        } else if ("java.util.Map".equals(cls.getName())) {
            par = getMapData(c.getName() + "." + method.getName());
        } else if (equalsAny(cls.getName(), "java.util.List", "java.util.ArrayList", "java.util.Collection")) {
            par = assignmentCollection(method, index, par, o -> {
                List<Object> objects = new ArrayList<>();
                objects.add(o);
                return objects;
            });
        } else if (equalsAny(cls.getName(), "java.util.Set", "java.util.HashSet")) {
            par = assignmentCollection(method, index, par, o -> {
                Set<Object> objects = new HashSet<>();
                objects.add(o);
                return objects;
            });
        } else {
            // 适配部分带*导入的import无法add
            try {
                par = cls.newInstance();
                assignment(cls, par);
            } catch (InstantiationException e) {
                par = handleObject(cls, par, e);
            }
        }
        return par;
    }

    /** 处理数据类型 */
    private Object handleObject(Class cls, Object par, InstantiationException e) {
        // log.error("newInstance error", e);
        // 是否基本数据类型
        if (cls.isPrimitive()) {
            cls = PRIMITIVE_CLAZZ.get(cls.getName());
        }
        try {
            if (cls.equals(LocalDateTime.class)) {
                par = LocalDateTime.now();
            } else if (cls.equals(Date.class)) {
                par = new Date();
            } else {
                par = cls.getDeclaredConstructor(String.class).newInstance("1");
            }
        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e1) {
            log.error(cls.getName() + e1);
        }
        return par;
    }

    /** 为集合类赋值 */
    public Object assignmentCollection(Method method, int index, Object par, Function<Object, Object> function)
            throws InstantiationException, IllegalAccessException, IntrospectionException, InvocationTargetException {
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        Type genericParameterType = genericParameterTypes[index];
        if (!(genericParameterType instanceof ParameterizedTypeImpl)) {
            return null;
        }
        Type[] actualTypeArguments = ((ParameterizedTypeImpl) genericParameterType).getActualTypeArguments();
        Type actualTypeArgument = actualTypeArguments[0];
        String typeName = actualTypeArgument.getTypeName();
        try {
            Class<?> aClass = Class.forName(typeName);
            // 适配集合中的泛型为基础类型
            if (equalsAny(aClass, byte.class, Byte.class,
                    short.class, Short.class,
                    int.class, Integer.class)) {
                return 1;
            } else if (equalsAny(aClass, long.class, Long.class)) {
                return 1L;
            } else if (equalsAny(aClass, float.class, Float.class)) {
                return 1.0f;
            } else if (equalsAny(aClass, double.class, Double.class)) {
                return 1.0;
            } else if (equalsAny(aClass, boolean.class, Boolean.class)) {
                return true;
            } else if (equalsAny(aClass, char.class, Character.class)) {
                return '1';
            } else {
                // 适配集合中的泛型为Object
                par = aClass.newInstance();
            }
            par = assignment(aClass, par);
            par = function.apply(par);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return par;
    }

    /** 获得xml文件中的#{}中的key值 */
    private Map<String, Object> getMapData(String url) {
        Map<String, Object> resultMap = new HashMap<>();
        Map<String, Object> parameterMap = new HashMap<>();
        BoundSql sql = null;
        parameterMap.put("tranSno", "111");
        try {
            sql = configuration.getMappedStatement(url).getBoundSql(parameterMap);
        } catch (Exception exception) {
            log.error("", exception);
        }
        if (sql != null) {
            List<ParameterMapping> parameterMappings = sql.getParameterMappings();
            parameterMappings.forEach(parameterMapping -> {
                // 处理数据类型
                Class<?> cls = parameterMapping.getJavaType();
                Object par = new Object();
                par = handleObject(cls, par, null);
                String key = parameterMapping.getProperty();
                if ("startRow".equals(key)) {
                    resultMap.put(key, 1);
                } else if ("endRow".equals(key)) {
                    resultMap.put(key, 2);
                } else {
                    resultMap.put(key, par);
                }
            });
        }
        return resultMap;
    }

    /** 为对象赋值 */
    private Object assignment(Class<?> beanClass, Object o)
            throws IntrospectionException, InvocationTargetException, IllegalAccessException {
        BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor x : propertyDescriptors) {
            Method method = x.getWriteMethod();
            Method readMethhod = x.getReadMethod();
            if (method == null) {
                // public final native java.lang.Class java.lang.Object.getClass()
                readMethhod.invoke(o);
                continue;
            }
            method.setAccessible(true);
            if (x.getPropertyType().equals(String.class)) {
                if ("uuid".equals(x.getName()) || "id".equals(x.getName())) {
                    method.invoke(o, UUID.randomUUID().toString().substring(0, 30));
                } else if (x.getName().toLowerCase(Locale.ROOT).contains("time")
                        || x.getName().toLowerCase(Locale.ROOT).contains("date")) {
                    method.invoke(o, "3023-05-19 11:11:11");
                } else {
                    method.invoke(o, "1");
                }
            } else if (x.getPropertyType().equals(BigInteger.class)) {
                Random random = new Random();
                method.invoke(o, BigInteger.valueOf(Math.abs(random.nextLong())));
            } else if (x.getPropertyType().equals(BigDecimal.class)) {
                method.invoke(o, new BigDecimal(0));
            } else if (x.getPropertyType().equals(Date.class)) {
                method.invoke(o, new Date());
            } else if (x.getPropertyType().equals(LocalDateTime.class)) {
                method.invoke(o, LocalDateTime.now());
            } else if (equalsAny(x.getPropertyType(),
                    byte.class, Byte.class,
                    short.class, Short.class,
                    int.class, Integer.class)) {
                method.invoke(o, 1);
            } else if (equalsAny(x.getPropertyType(),
                    long.class, Long.class)) {
                Random random = new Random();
                method.invoke(o, Math.abs(random.nextLong()));
            } else if (equalsAny(x.getPropertyType(),
                    float.class, Float.class)) {
                method.invoke(o, 1.0f);
            } else if (equalsAny(x.getPropertyType(),
                    double.class, Double.class)) {
                method.invoke(o, 1.0);
            } else if (equalsAny(x.getPropertyType(), boolean.class, Boolean.class)) {
                method.invoke(o, true);
            } else if (equalsAny(x.getPropertyType(), char.class, Character.class)) {
                method.invoke(o, '1');
            } else if (equalsAny(x.getPropertyType(), Set.class)) {
                // 适配特殊语句处理
                Set<Object> objects = new HashSet<>();
                objects.add(1);
                method.invoke(o, objects);
            }
            readMethhod.invoke(o);
        }
        return o;
    }

    /** 获取文件内容 */
    private String getFileContent(String path) throws IOException {
        File file = new File(path);
        File[] tempList = file.listFiles();
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < tempList.length; i++) {
            if (tempList[i].isDirectory()) {
                String str2 = getFileContent(tempList[i].getPath());
                stringBuilder.append(str2);
            } else {
                String fileName = tempList[i].getName();
                FILE_NAME.add(fileName.replaceAll("\\.java", ""));
                String str = readFile(path + "/" + fileName);
                stringBuilder.append(str);
            }
        }
        return stringBuilder.toString();
    }

    /** 读取文件 */
    private String readFile(String fileName) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader(fileName));
        String str;
        StringBuilder stringBuilder = new StringBuilder();
        while ((str = bufferedReader.readLine()) != null) {
            stringBuilder.append(str).append("\n");
        }
        bufferedReader.close();
        return stringBuilder.toString();
    }

    /** 返回正则匹配的内容 */
    private String matchMethod(String pattern, String matchString) {
        Pattern r = Pattern.compile(pattern);
        Matcher matcher = r.matcher(matchString);
        StringBuilder stringBuilder = new StringBuilder();
        while (matcher.find()) {
            String string = matcher.group(0);
            stringBuilder.append(string);
        }
        return stringBuilder.toString();
    }

    public boolean equalsAny(Object o, Object... objArr) {
        for (Object other : objArr) {
            if (Objects.equals(o, other)) {
                return true;
            }
        }
        return false;
    }
}
